home *** CD-ROM | disk | FTP | other *** search
- /*
- * serial_unix.cpp - Serial device driver, Unix specific stuff
- *
- * Basilisk II (C) 1997-2001 Christian Bauer
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
- #include "sysdeps.h"
-
- #include <sys/ioctl.h>
- #include <sys/stat.h>
- #include <pthread.h>
- #include <semaphore.h>
- #include <termios.h>
- #ifdef __linux__
- #include <linux/lp.h>
- #include <linux/major.h>
- #include <linux/kdev_t.h>
- #endif
-
- #include "cpu_emulation.h"
- #include "main.h"
- #include "macos_util.h"
- #include "prefs.h"
- #include "serial.h"
- #include "serial_defs.h"
-
- #define DEBUG 0
- #include "debug.h"
-
- #define MONITOR 0
-
-
- // Missing functions
- #ifndef HAVE_CFMAKERAW
- static int cfmakeraw(struct termios *termios_p)
- {
- termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
- termios_p->c_oflag &= ~OPOST;
- termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
- termios_p->c_cflag &= ~(CSIZE|PARENB);
- termios_p->c_cflag |= CS8;
- }
- #endif
-
-
- // Driver private variables
- class XSERDPort : public SERDPort {
- public:
- XSERDPort(const char *dev)
- {
- device_name = dev;
- is_parallel = false;
- fd = -1;
- input_thread_active = output_thread_active = false;
-
- pthread_attr_init(&thread_attr);
- #if defined(_POSIX_THREAD_PRIORITY_SCHEDULING)
- if (geteuid() == 0) {
- pthread_attr_setinheritsched(&thread_attr, PTHREAD_EXPLICIT_SCHED);
- pthread_attr_setschedpolicy(&thread_attr, SCHED_FIFO);
- struct sched_param fifo_param;
- fifo_param.sched_priority = (sched_get_priority_min(SCHED_FIFO) + sched_get_priority_max(SCHED_FIFO)) / 2 + 2;
- pthread_attr_setschedparam(&thread_attr, &fifo_param);
- }
- #endif
- }
-
- virtual ~XSERDPort()
- {
- if (input_thread_active) {
- input_thread_cancel = true;
- #ifdef HAVE_PTHREAD_CANCEL
- pthread_cancel(input_thread);
- #endif
- pthread_join(input_thread, NULL);
- sem_destroy(&input_signal);
- input_thread_active = false;
- }
- if (output_thread_active) {
- output_thread_cancel = true;
- #ifdef HAVE_PTHREAD_CANCEL
- pthread_cancel(output_thread);
- #endif
- pthread_join(output_thread, NULL);
- sem_destroy(&output_signal);
- output_thread_active = false;
- }
- }
-
- virtual int16 open(uint16 config);
- virtual int16 prime_in(uint32 pb, uint32 dce);
- virtual int16 prime_out(uint32 pb, uint32 dce);
- virtual int16 control(uint32 pb, uint32 dce, uint16 code);
- virtual int16 status(uint32 pb, uint32 dce, uint16 code);
- virtual int16 close(void);
-
- private:
- bool configure(uint16 config);
- void set_handshake(uint32 s, bool with_dtr);
- static void *input_func(void *arg);
- static void *output_func(void *arg);
-
- const char *device_name; // Device name
- bool is_parallel; // Flag: Port is parallel
- int fd; // FD of device
-
- bool io_killed; // Flag: KillIO called, I/O threads must not call deferred tasks
- bool quitting; // Flag: Quit threads
-
- pthread_attr_t thread_attr; // Input/output thread attributes
-
- bool input_thread_active; // Flag: Input thread installed
- volatile bool input_thread_cancel; // Flag: Cancel input thread
- pthread_t input_thread; // Data input thread
- sem_t input_signal; // Signal for input thread: execute command
- uint32 input_pb; // Command parameter for input thread
-
- bool output_thread_active; // Flag: Output thread installed
- volatile bool output_thread_cancel; // Flag: Cancel output thread
- pthread_t output_thread; // Data output thread
- sem_t output_signal; // Signal for output thread: execute command
- uint32 output_pb; // Command parameter for output thread
-
- struct termios mode; // Terminal configuration
- };
-
-
- /*
- * Initialization
- */
-
- void SerialInit(void)
- {
- // Read serial preferences and create structs for both ports
- the_serd_port[0] = new XSERDPort(PrefsFindString("seriala"));
- the_serd_port[1] = new XSERDPort(PrefsFindString("serialb"));
- }
-
-
- /*
- * Deinitialization
- */
-
- void SerialExit(void)
- {
- delete (XSERDPort *)the_serd_port[0];
- delete (XSERDPort *)the_serd_port[1];
- }
-
-
- /*
- * Open serial port
- */
-
- int16 XSERDPort::open(uint16 config)
- {
- // Don't open NULL name devices
- if (device_name == NULL)
- return openErr;
-
- // Init variables
- io_killed = false;
- quitting = false;
-
- // Open port
- fd = ::open(device_name, O_RDWR);
- if (fd < 0)
- goto open_error;
-
- #if defined(__linux__)
- // Parallel port?
- struct stat st;
- if (fstat(fd, &st) == 0)
- if (S_ISCHR(st.st_mode))
- is_parallel = (MAJOR(st.st_rdev) == LP_MAJOR);
- #elif defined(__FreeBSD__) || defined(__NetBSD__)
- // Parallel port?
- struct stat st;
- if (fstat(fd, &st) == 0)
- if (S_ISCHR(st.st_mode))
- is_parallel = ((st.st_rdev >> 16) == 16);
- #endif
-
- // Configure port for raw mode
- if (!is_parallel) {
- if (tcgetattr(fd, &mode) < 0)
- goto open_error;
- cfmakeraw(&mode);
- mode.c_cflag |= HUPCL;
- mode.c_cc[VMIN] = 1;
- mode.c_cc[VTIME] = 0;
- tcsetattr(fd, TCSAFLUSH, &mode);
- }
- configure(config);
-
- // Start input/output threads
- input_thread_cancel = false;
- output_thread_cancel = false;
- if (sem_init(&input_signal, 0, 0) < 0)
- goto open_error;
- if (sem_init(&output_signal, 0, 0) < 0)
- goto open_error;
- input_thread_active = (pthread_create(&input_thread, &thread_attr, input_func, this) == 0);
- output_thread_active = (pthread_create(&output_thread, &thread_attr, output_func, this) == 0);
- if (!input_thread_active || !output_thread_active)
- goto open_error;
- return noErr;
-
- open_error:
- if (input_thread_active) {
- input_thread_cancel = true;
- #ifdef HAVE_PTHREAD_CANCEL
- pthread_cancel(input_thread);
- #endif
- pthread_join(input_thread, NULL);
- sem_destroy(&input_signal);
- input_thread_active = false;
- }
- if (output_thread_active) {
- output_thread_cancel = true;
- #ifdef HAVE_PTHREAD_CANCEL
- pthread_cancel(output_thread);
- #endif
- pthread_join(output_thread, NULL);
- sem_destroy(&output_signal);
- output_thread_active = false;
- }
- if (fd > 0) {
- ::close(fd);
- fd = -1;
- }
- return openErr;
- }
-
-
- /*
- * Read data from port
- */
-
- int16 XSERDPort::prime_in(uint32 pb, uint32 dce)
- {
- // Send input command to input_thread
- read_done = false;
- read_pending = true;
- input_pb = pb;
- WriteMacInt32(input_dt + serdtDCE, dce);
- sem_post(&input_signal);
- return 1; // Command in progress
- }
-
-
- /*
- * Write data to port
- */
-
- int16 XSERDPort::prime_out(uint32 pb, uint32 dce)
- {
- // Send output command to output_thread
- write_done = false;
- write_pending = true;
- output_pb = pb;
- WriteMacInt32(output_dt + serdtDCE, dce);
- sem_post(&output_signal);
- return 1; // Command in progress
- }
-
-
- /*
- * Control calls
- */
-
- int16 XSERDPort::control(uint32 pb, uint32 dce, uint16 code)
- {
- switch (code) {
- case 1: // KillIO
- io_killed = true;
- if (!is_parallel)
- tcflush(fd, TCIOFLUSH);
- while (read_pending || write_pending)
- usleep(10000);
- io_killed = false;
- return noErr;
-
- case kSERDConfiguration:
- if (configure(ReadMacInt16(pb + csParam)))
- return noErr;
- else
- return paramErr;
-
- case kSERDInputBuffer:
- return noErr; // Not supported under Unix
-
- case kSERDSerHShake:
- set_handshake(pb + csParam, false);
- return noErr;
-
- case kSERDSetBreak:
- if (!is_parallel)
- tcsendbreak(fd, 0);
- return noErr;
-
- case kSERDClearBreak:
- return noErr;
-
- case kSERDBaudRate: {
- if (is_parallel)
- return noErr;
- uint16 rate = ReadMacInt16(pb + csParam);
- speed_t baud_rate;
- if (rate <= 50) {
- rate = 50; baud_rate = B50;
- } else if (rate <= 75) {
- rate = 75; baud_rate = B75;
- } else if (rate <= 110) {
- rate = 110; baud_rate = B110;
- } else if (rate <= 134) {
- rate = 134; baud_rate = B134;
- } else if (rate <= 150) {
- rate = 150; baud_rate = B150;
- } else if (rate <= 200) {
- rate = 200; baud_rate = B200;
- } else if (rate <= 300) {
- rate = 300; baud_rate = B300;
- } else if (rate <= 600) {
- rate = 600; baud_rate = B600;
- } else if (rate <= 1200) {
- rate = 1200; baud_rate = B1200;
- } else if (rate <= 1800) {
- rate = 1800; baud_rate = B1800;
- } else if (rate <= 2400) {
- rate = 2400; baud_rate = B2400;
- } else if (rate <= 4800) {
- rate = 4800; baud_rate = B4800;
- } else if (rate <= 9600) {
- rate = 9600; baud_rate = B9600;
- } else if (rate <= 19200) {
- rate = 19200; baud_rate = B19200;
- } else if (rate <= 38400) {
- rate = 38400; baud_rate = B38400;
- } else if (rate <= 57600) {
- rate = 57600; baud_rate = B57600;
- } else {
- // Just for safety in case someone wants a rate between 57600 and 65535
- rate = 57600; baud_rate = B57600;
- }
- WriteMacInt16(pb + csParam, rate);
- cfsetispeed(&mode, B115200);
- cfsetospeed(&mode, B115200);
- tcsetattr(fd, TCSANOW, &mode);
- return noErr;
- }
-
- case kSERDHandshake:
- case kSERDHandshakeRS232:
- set_handshake(pb + csParam, true);
- return noErr;
-
- case kSERDMiscOptions:
- if (is_parallel)
- return noErr;
- if (ReadMacInt8(pb + csParam) & kOptionPreserveDTR)
- mode.c_cflag &= ~HUPCL;
- else
- mode.c_cflag |= HUPCL;
- tcsetattr(fd, TCSANOW, &mode);
- return noErr;
-
- case kSERDAssertDTR: {
- if (is_parallel)
- return noErr;
- unsigned int status = TIOCM_DTR;
- ioctl(fd, TIOCMBIS, &status);
- return noErr;
- }
-
- case kSERDNegateDTR: {
- if (is_parallel)
- return noErr;
- unsigned int status = TIOCM_DTR;
- ioctl(fd, TIOCMBIC, &status);
- return noErr;
- }
-
- case kSERDSetPEChar:
- case kSERDSetPEAltChar:
- return noErr; // Not supported under Unix
-
- case kSERDResetChannel:
- if (!is_parallel)
- tcflush(fd, TCIOFLUSH);
- return noErr;
-
- case kSERDAssertRTS: {
- if (is_parallel)
- return noErr;
- unsigned int status = TIOCM_RTS;
- ioctl(fd, TIOCMBIS, &status);
- return noErr;
- }
-
- case kSERDNegateRTS: {
- if (is_parallel)
- return noErr;
- unsigned int status = TIOCM_RTS;
- ioctl(fd, TIOCMBIC, &status);
- return noErr;
- }
-
- case kSERD115KBaud:
- if (is_parallel)
- return noErr;
- cfsetispeed(&mode, B115200);
- cfsetospeed(&mode, B115200);
- tcsetattr(fd, TCSANOW, &mode);
- return noErr;
-
- case kSERD230KBaud:
- case kSERDSetHighSpeed:
- if (is_parallel)
- return noErr;
- cfsetispeed(&mode, B230400);
- cfsetospeed(&mode, B230400);
- tcsetattr(fd, TCSANOW, &mode);
- return noErr;
-
- default:
- printf("WARNING: SerialControl(): unimplemented control code %d\n", code);
- return controlErr;
- }
- }
-
-
- /*
- * Status calls
- */
-
- int16 XSERDPort::status(uint32 pb, uint32 dce, uint16 code)
- {
- switch (code) {
- case kSERDInputCount: {
- int num;
- ioctl(fd, FIONREAD, &num);
- WriteMacInt32(pb + csParam, num);
- return noErr;
- }
-
- case kSERDStatus: {
- uint32 p = pb + csParam;
- WriteMacInt8(p + staCumErrs, cum_errors);
- cum_errors = 0;
- WriteMacInt8(p + staXOffSent, 0);
- WriteMacInt8(p + staXOffHold, 0);
- WriteMacInt8(p + staRdPend, read_pending);
- WriteMacInt8(p + staWrPend, write_pending);
- if (is_parallel) {
- WriteMacInt8(p + staCtsHold, 0);
- WriteMacInt8(p + staDsrHold, 0);
- WriteMacInt8(p + staModemStatus, dsrEvent | dcdEvent | ctsEvent);
- } else {
- unsigned int status;
- ioctl(fd, TIOCMGET, &status);
- WriteMacInt8(p + staCtsHold, status & TIOCM_CTS ? 0 : 1);
- WriteMacInt8(p + staDsrHold, status & TIOCM_DTR ? 0 : 1);
- WriteMacInt8(p + staModemStatus,
- (status & TIOCM_DSR ? dsrEvent : 0)
- | (status & TIOCM_RI ? riEvent : 0)
- | (status & TIOCM_CD ? dcdEvent : 0)
- | (status & TIOCM_CTS ? ctsEvent : 0));
- }
- return noErr;
- }
-
- default:
- printf("WARNING: SerialStatus(): unimplemented status code %d\n", code);
- return statusErr;
- }
- }
-
-
- /*
- * Close serial port
- */
-
- int16 XSERDPort::close()
- {
- // Kill threads
- if (input_thread_active) {
- quitting = true;
- sem_post(&input_signal);
- pthread_join(input_thread, NULL);
- input_thread_active = false;
- sem_destroy(&input_signal);
- }
- if (output_thread_active) {
- quitting = true;
- sem_post(&output_signal);
- pthread_join(output_thread, NULL);
- output_thread_active = false;
- sem_destroy(&output_signal);
- }
-
- // Close port
- if (fd > 0)
- ::close(fd);
- fd = -1;
- return noErr;
- }
-
-
- /*
- * Configure serial port with MacOS config word
- */
-
- bool XSERDPort::configure(uint16 config)
- {
- D(bug(" configure %04x\n", config));
- if (is_parallel)
- return true;
-
- // Set number of stop bits
- switch (config & 0xc000) {
- case stop10:
- mode.c_cflag &= ~CSTOPB;
- break;
- case stop20:
- mode.c_cflag |= CSTOPB;
- break;
- default:
- return false;
- }
-
- // Set parity mode
- switch (config & 0x3000) {
- case noParity:
- mode.c_iflag &= ~INPCK;
- mode.c_oflag &= ~PARENB;
- break;
- case oddParity:
- mode.c_iflag |= INPCK;
- mode.c_oflag |= PARENB;
- mode.c_oflag |= PARODD;
- break;
- case evenParity:
- mode.c_iflag |= INPCK;
- mode.c_oflag |= PARENB;
- mode.c_oflag &= ~PARODD;
- break;
- default:
- return false;
- }
-
- // Set number of data bits
- switch (config & 0x0c00) {
- case data5:
- mode.c_cflag = mode.c_cflag & ~CSIZE | CS5;
- break;
- case data6:
- mode.c_cflag = mode.c_cflag & ~CSIZE | CS6;
- break;
- case data7:
- mode.c_cflag = mode.c_cflag & ~CSIZE | CS7;
- break;
- case data8:
- mode.c_cflag = mode.c_cflag & ~CSIZE | CS8;
- break;
- }
-
- // Set baud rate
- speed_t baud_rate;
- switch (config & 0x03ff) {
- case baud150: baud_rate = B150; break;
- case baud300: baud_rate = B300; break;
- case baud600: baud_rate = B600; break;
- case baud1200: baud_rate = B1200; break;
- case baud1800: baud_rate = B1800; break;
- case baud2400: baud_rate = B2400; break;
- case baud4800: baud_rate = B4800; break;
- case baud9600: baud_rate = B9600; break;
- case baud19200: baud_rate = B19200; break;
- case baud38400: baud_rate = B38400; break;
- case baud57600: baud_rate = B57600; break;
- default:
- return false;
- }
- cfsetispeed(&mode, baud_rate);
- cfsetospeed(&mode, baud_rate);
- tcsetattr(fd, TCSANOW, &mode);
- return true;
- }
-
-
- /*
- * Set serial handshaking
- */
-
- void XSERDPort::set_handshake(uint32 s, bool with_dtr)
- {
- D(bug(" set_handshake %02x %02x %02x %02x %02x %02x %02x %02x\n",
- ReadMacInt8(s + 0), ReadMacInt8(s + 1), ReadMacInt8(s + 2), ReadMacInt8(s + 3),
- ReadMacInt8(s + 4), ReadMacInt8(s + 5), ReadMacInt8(s + 6), ReadMacInt8(s + 7)));
- if (is_parallel)
- return;
-
- if (with_dtr) {
- if (ReadMacInt8(s + shkFCTS) || ReadMacInt8(s + shkFDTR))
- mode.c_cflag |= CRTSCTS;
- else
- mode.c_cflag &= ~CRTSCTS;
- } else {
- if (ReadMacInt8(s + shkFCTS))
- mode.c_cflag |= CRTSCTS;
- else
- mode.c_cflag &= ~CRTSCTS;
- }
-
- D(bug(" %sware flow control\n", mode.c_cflag & CRTSCTS ? "hard" : "soft"));
- tcsetattr(fd, TCSANOW, &mode);
- }
-
-
- /*
- * Data input thread
- */
-
- void *XSERDPort::input_func(void *arg)
- {
- XSERDPort *s = (XSERDPort *)arg;
- while (!s->input_thread_cancel) {
-
- // Wait for commands
- sem_wait(&s->input_signal);
- if (s->quitting)
- break;
-
- // Execute command
- void *buf = Mac2HostAddr(ReadMacInt32(s->input_pb + ioBuffer));
- uint32 length = ReadMacInt32(s->input_pb + ioReqCount);
- D(bug("input_func waiting for %ld bytes of data...\n", length));
- int32 actual = read(s->fd, buf, length);
- D(bug(" %ld bytes received\n", actual));
-
- #if MONITOR
- bug("Receiving serial data:\n");
- uint8 *adr = (uint8 *)buf;
- for (int i=0; i<actual; i++) {
- bug("%02x ", adr[i]);
- }
- bug("\n");
- #endif
-
- // KillIO called? Then simply return
- if (s->io_killed) {
-
- WriteMacInt16(s->input_pb + ioResult, uint16(abortErr));
- WriteMacInt32(s->input_pb + ioActCount, 0);
- s->read_pending = s->read_done = false;
-
- } else {
-
- // Set error code
- if (actual >= 0) {
- WriteMacInt32(s->input_pb + ioActCount, actual);
- WriteMacInt32(s->input_dt + serdtResult, noErr);
- } else {
- WriteMacInt32(s->input_pb + ioActCount, 0);
- WriteMacInt32(s->input_dt + serdtResult, uint16(readErr));
- }
-
- // Trigger serial interrupt
- D(bug(" triggering serial interrupt\n"));
- s->read_done = true;
- SetInterruptFlag(INTFLAG_SERIAL);
- TriggerInterrupt();
- }
- }
- return NULL;
- }
-
-
- /*
- * Data output thread
- */
-
- void *XSERDPort::output_func(void *arg)
- {
- XSERDPort *s = (XSERDPort *)arg;
- while (!s->output_thread_cancel) {
-
- // Wait for commands
- sem_wait(&s->output_signal);
- if (s->quitting)
- break;
-
- // Execute command
- void *buf = Mac2HostAddr(ReadMacInt32(s->output_pb + ioBuffer));
- uint32 length = ReadMacInt32(s->output_pb + ioReqCount);
- D(bug("output_func transmitting %ld bytes of data...\n", length));
-
- #if MONITOR
- bug("Sending serial data:\n");
- uint8 *adr = (uint8 *)buf;
- for (int i=0; i<length; i++) {
- bug("%02x ", adr[i]);
- }
- bug("\n");
- #endif
-
- int32 actual = write(s->fd, buf, length);
- D(bug(" %ld bytes transmitted\n", actual));
-
- // KillIO called? Then simply return
- if (s->io_killed) {
-
- WriteMacInt16(s->output_pb + ioResult, uint16(abortErr));
- WriteMacInt32(s->output_pb + ioActCount, 0);
- s->write_pending = s->write_done = false;
-
- } else {
-
- // Set error code
- if (actual >= 0) {
- WriteMacInt32(s->output_pb + ioActCount, actual);
- WriteMacInt32(s->output_dt + serdtResult, noErr);
- } else {
- WriteMacInt32(s->output_pb + ioActCount, 0);
- WriteMacInt32(s->output_dt + serdtResult, uint16(writErr));
- }
-
- // Trigger serial interrupt
- D(bug(" triggering serial interrupt\n"));
- s->write_done = true;
- SetInterruptFlag(INTFLAG_SERIAL);
- TriggerInterrupt();
- }
- }
- return NULL;
- }
-